<?php
namespace lib\JhWechat;

use Cross\Core\Helper;

/**
 * @Auth: JH <hu@lunaz.cn>
 * Class Base
 * @package lib\JhWechat
 */
class Base
{

    const API_DEFAULT = 'https://api.weixin.qq.com/cgi-bin';
    const API_BASE = 'https://api.weixin.qq.com';
    protected static $accessToken;
    protected static $config;
    protected static $debug = false;
    private static $file_cache = false;
    private static $form = array();
    private static $curl = null;

    function __construct()
    {
        self::$debug = isset(self::$config['debug']) ? self::$config['debug'] : false;
        self::$file_cache = isset(self::$config['file_cache']) ? self::$config['file_cache'] : false;
    }

    protected function getToken()
    {
        if (!self::$accessToken) {
            $file_cache = self::getCachePath();
            if ($file_cache && is_file($file_cache)) {
                $cache = require $file_cache;
                if ($cache['access_token'] && $cache['time'] > time()) {
                    return self::$accessToken = $cache['access_token'];
                }
            }

            if (empty(self::$config)) {
                throw new Exception('请提供微信公众接口凭据配置');
            }
            $api = self::makeUrl(
                '/token',
                array(
                    'grant_type' => 'client_credential',
                    'appid' => self::$config['appId'],
                    'secret' => self::$config['appSecret']
                )
            );
            $result = self::http($api);
            if (isset($result['access_token'])) {
                if (self::$file_cache) {
                    self::setCache($result['access_token'], time() + ($result['expires_in'] - 100));
                }
                return self::$accessToken = $result['access_token'];
            } else {
                throw new Exception('获取TOKEN失败');
            }
        }
        return self::$accessToken;
    }

    /**
     * @return array
     */
    protected function getAccessToken()
    {
        return array(
            'access_token' => $this->getToken()
        );
    }

    /**
     * @param $token
     * @param $time
     */
    private static function setCache($token, $time)
    {
        $file_cache = self::getCachePath();
        if (!is_file($file_cache)) {
            mkdir(dirname($file_cache), 0755, true);
            file_put_contents($file_cache, '');
        }
        if (is_file($file_cache) && !is_writable($file_cache)) {
            chmod($file_cache, 0666);
        }
        $data = <<<cache
<?php
return array('access_token' => '%s','time' => %u);
cache;
        file_put_contents($file_cache, sprintf($data, $token, $time));
    }

    protected static function getCachePath()
    {
        return PROJECT_PATH . '/cache/wechat/access_token_' . self::$config['appId'] . '.php';
    }

    /**
     * 生成XML
     *
     * @param array $array
     * @param string $root
     *
     * @return string
     */
    protected function makeXML(array $array, $root = 'xml')
    {
        $xml = '';
        $cdata = function ($str) {
            return sprintf('<![CDATA[%s]]>', preg_replace("/[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]/", '', $str));
        };

        foreach ($array as $key => $value) {
            if (is_array($value)) {
                $data = $this->makeXML($value, false);
            } elseif (!is_int($value)) {
                $data = $cdata($value);
            } else {
                $data = $value;
            }
            $xml .= sprintf("<%s>%s</%s>", $key, $data, $key);
        }

        return $root === false ? $xml : sprintf("<%s>%s</%s>", $root, $xml, $root);
    }


    /**
     * 解析XML为数组
     *
     * @param string $xml
     *
     * @return array
     * @throws Exception
     */
    protected static function parseXML($xml)
    {
        if (stripos($xml, '<xml>') === false) {
            throw new Exception('XML数据不合法');
        }
        return (array)simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
    }

    /**
     * 生成微信API——Url
     *
     * @param $path
     * @param array $params
     * @param string $host
     *
     * @return string
     */
    protected static function makeUrl($path, array $params, $host = '')
    {
        $link = stripos($host, '?') ? '&' : '?';
        if ('' === $host) {
            $host = self::API_DEFAULT;
        }
        return $host . $path . $link . http_build_query($params);
    }

    /**
     * @param $url
     * @param string|array $post_params
     * @param int $retry
     *
     * @return array|bool|mixed
     * @throws Exception
     */
    protected static function http($url, $post_params = '', $retry = 3)
    {
        $post_data = $post_params ? self::postFields($post_params) : null;
        !self::$curl && self::$curl = curl_init();
        curl_setopt_array(
            self::$curl,
            array(
                CURLOPT_DNS_CACHE_TIMEOUT => 3600,
                CURLOPT_SSL_VERIFYHOST => false,
                CURLOPT_SSL_VERIFYPEER => false,
                CURLOPT_HEADER => false,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_URL => $url,
                CURLOPT_CUSTOMREQUEST => $post_params ? 'POST' : 'GET',
                CURLOPT_TIMEOUT => 60,
                CURLOPT_POST => $post_params !== '',
                CURLOPT_POSTFIELDS => $post_data
            )
        );

        $result = curl_exec(self::$curl);
        if (curl_errno(self::$curl)) {
            if ($retry > 0) {
                return self::http($url, $post_params, --$retry);
            }
            throw new Exception('Curl error: ' . curl_error(self::$curl));
        } else {
            $status = curl_getinfo(self::$curl);
            if ((int)$status['http_code'] !== 200) {
                return self::http($url, $post_params, --$retry);
            }
            $result = self::parseJson($result, stripos($status['content_type'], 'json') === false);
            if (isset($result['errcode']) && !self::$debug) {
                $diff = array_diff_key($result, array('errcode' => 1, 'errmsg' => 2));
                if (empty($diff)) {
                    if ($result['errcode'] == 0) {
                        return true;
                    }
                    if ($result['errcode'] == 40001 && self::$file_cache) {
                        self::setCache('', 0);
                    }
                    self::errLog($result);
                    return false;
                }
                return $diff;
            }
            return $result;
        }
    }

    static function parseJson($data, $check = true)
    {
        $result = json_decode($data, true);
        if ($check) {
            if (json_last_error() === JSON_ERROR_NONE) {
                return $result;
            }
            return $data;
        }
        return $result;
    }

    protected function addMultiFormData(array $form)
    {
        self::$form += $form;
    }

    private static function postFields($data)
    {
        if (is_array($data)) {
            return self::makeJson($data);
        } elseif ($data) {
            return self::makeFile($data);
        } else {
            return null;
        }
    }

    private static function makeFile($file)
    {
        if (!is_file($file)) {
            throw new Exception("没找到文件：{$file}");
        }
        !self::$curl && self::$curl = curl_init();
        if (function_exists('curl_file_create')) {
            curl_setopt(self::$curl, CURLOPT_SAFE_UPLOAD, true);
            $form = array(
                'media' => curl_file_create($file)
            );
        } else {
            version_compare(PHP_VERSION, '5.5', '>') ||
            curl_setopt(self::$curl, CURLOPT_SAFE_UPLOAD, false);
            $file_info = self::getFileInfo($file);
            $info = array(
                $file_info['basename'],
                self::fileMime($file_info['extension'])
            );
            $form = array(
                'media' => "@{$file};postname={$info[0]};mime={$info[1]}"
            );
        }
        if (self::$form) {
            $form += self::$form;
        }
        return $form;
    }

    protected static function getFileInfo($file_path, $key = 'all')
    {
        $info = pathinfo($file_path);
        if ($key === 'all') {
            return $info;
        }
        if (isset($info[$key])) {
            return $info[$key];
        }
        return false;
    }

    private static function fileMime($ext)
    {
        $mime = array(
            //applications
            'ai' => 'application/postscript',
            'eps' => 'application/postscript',
            'exe' => 'application/octet-stream',
            'doc' => 'application/vnd.ms-word',
            'xls' => 'application/vnd.ms-excel',
            'ppt' => 'application/vnd.ms-powerpoint',
            'pps' => 'application/vnd.ms-powerpoint',
            'pdf' => 'application/pdf',
            'xml' => 'application/xml',
            'odt' => 'application/vnd.oasis.opendocument.text',
            'swf' => 'application/x-shockwave-flash',
            // archives
            'gz' => 'application/x-gzip',
            'tgz' => 'application/x-gzip',
            'bz' => 'application/x-bzip2',
            'bz2' => 'application/x-bzip2',
            'tbz' => 'application/x-bzip2',
            'zip' => 'application/zip',
            'rar' => 'application/x-rar',
            'tar' => 'application/x-tar',
            '7z' => 'application/x-7z-compressed',
            // texts
            'txt' => 'text/plain',
            'php' => 'text/x-php',
            'html' => 'text/html',
            'htm' => 'text/html',
            'js' => 'text/javascript',
            'css' => 'text/css',
            'rtf' => 'text/rtf',
            'rtfd' => 'text/rtfd',
            'py' => 'text/x-python',
            'java' => 'text/x-java-source',
            'rb' => 'text/x-ruby',
            'sh' => 'text/x-shellscript',
            'pl' => 'text/x-perl',
            'sql' => 'text/x-sql',
            // images
            'bmp' => 'image/x-ms-bmp',
            'jpg' => 'image/jpeg',
            'jpeg' => 'image/jpeg',
            'gif' => 'image/gif',
            'png' => 'image/png',
            'tif' => 'image/tiff',
            'tiff' => 'image/tiff',
            'tga' => 'image/x-targa',
            'psd' => 'image/vnd.adobe.photoshop',
            //audio
            'mp3' => 'audio/mpeg',
            'mid' => 'audio/midi',
            'ogg' => 'audio/ogg',
            'mp4a' => 'audio/mp4',
            'wav' => 'audio/wav',
            'wma' => 'audio/x-ms-wma',
            // video
            'avi' => 'video/x-msvideo',
            'dv' => 'video/x-dv',
            'mp4' => 'video/mp4',
            'mpeg' => 'video/mpeg',
            'mpg' => 'video/mpeg',
            'mov' => 'video/quicktime',
            'wm' => 'video/x-ms-wmv',
            'flv' => 'video/x-flv',
            'mkv' => 'video/x-matroska'
        );
        if (isset($mime[$ext])) {
            return $mime[$ext];
        }
        return 'application/octet-stream';
    }


    /**
     * 编译JSON
     *
     * @param array $array
     *
     * @return string
     */
    protected static function makeJson(array $array)
    {
        return json_encode($array, JSON_UNESCAPED_UNICODE);
    }

    private static function errLog($data)
    {
        self::writeLog($data, 'err');
    }

    static function log($data)
    {
        self::writeLog($data);
    }

    private static function writeLog($data, $type = 'log')
    {
        $file = PROJECT_PATH . '/log/wechat/' . $type . '_' . self::$config['appId'] . '_' . date('Ymd') . '.php';
        if (!is_file($file) && Helper::mkfile($file)) {
            file_put_contents($file, '<?php \n');
        }
        is_array($data) && $data = json_encode($data, JSON_UNESCAPED_UNICODE);
        $data = sprintf("\n#####\n# %s  %s\n# %s", date('Y-m-d H:i:s'), $_SERVER['REQUEST_URI'], $data);
        file_put_contents($file, $data, FILE_APPEND | LOCK_EX);
    }

    function __destruct()
    {
        if (is_resource(self::$curl)) {
            curl_close(self::$curl);
        }

    }

}
